package cn.flying.cloud.base.common.excel;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.date.DateDateConverter;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.fastjson2.JSON;

/**
 * Excel工具类
 *
 * @author: admin
 * @date: 2022年11月13日 20:02
 * @version: 1.0
 */
public class ExcelUtil {


    /**
     * 将数据列表写入Excel文件
     *
     * @param outputStream 输出流
     * @param fileName     文件名
     * @param sheetName    Excel sheet 名
     * @param head         Excel head 头
     * @param data         数据列表
     * @param <T>          泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void writeExcel(OutputStream outputStream, String fileName, String sheetName, Class<T> head, List<T> data) throws IOException {
        // 这里需要设置不关闭流
        EasyExcel.write(outputStream, head)
                .autoCloseStream(Boolean.FALSE)
                .excelType(ExcelTypeEnum.XLSX)
                .useDefaultStyle(false)
                .registerConverter(new LocalDateTimeConverter())
                .registerConverter(new DateDateConverter())
                .sheet(sheetName)
                .doWrite(data);
    }


    /**
     * 根据模板导出，写入Excel文件
     *
     * @param outputStream 输出流
     * @param fileName     文件名
     * @param sheetName    Excel sheet 名
     * @param head         Excel head 头
     * @param data         数据列表
     * @param <T>          泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void writeExcel(OutputStream outputStream, String templatePath, String fileName, String sheetName, Class<T> head, List<T> data) throws IOException {
        // 这里需要设置不关闭流
        EasyExcel.write(outputStream, head)
                .withTemplate(templatePath)
                .autoCloseStream(Boolean.FALSE)
                .excelType(ExcelTypeEnum.XLSX)
                .useDefaultStyle(false)
                .registerConverter(new LocalDateTimeConverter())
                .registerConverter(new DateDateConverter())
                .sheet(sheetName)
                .doWrite(data);
    }

    /**
     * 根据参数，只导出指定列
     *
     * @param outputStream            输出流
     * @param fileName                文件名
     * @param sheetName               Excel sheet 名
     * @param head                    Excel head 头
     * @param data                    数据列表
     * @param excludeColumnFiledNames 指定的列
     * @param <T>                     泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void writeExcel(OutputStream outputStream, String fileName, String sheetName, Class<T> head, List<T> data, Set<String> excludeColumnFiledNames) throws IOException {
        // 这里需要设置不关闭流
        EasyExcel.write(outputStream, head)
                .autoCloseStream(Boolean.FALSE)
                .excelType(ExcelTypeEnum.XLSX)
                .useDefaultStyle(false)
                .registerConverter(new LocalDateTimeConverter())
                .registerConverter(new DateDateConverter())
                .excludeColumnFieldNames(excludeColumnFiledNames)
                .sheet(sheetName)
                .doWrite(data);
    }

    /**
     * 写入response对象
     *
     * @param response
     * @param fileName
     */
    public static void setResponse(HttpServletResponse response, String fileName) {
        try {
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
//        String fileName = URLEncoder.encode("data", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xls");
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = MapUtils.newHashMap();
            map.put("status", "failure");
            map.put("message", "写入文件失败" + e.getMessage());
            try {
                response.getWriter().println(JSON.toJSONString(map));
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    /**
     * 读取Excel，同步返回数据（不建议解析大数据量使用）
     *
     * @param inputStream 文件流
     * @param head        实体类映射
     * @param sheet       sheet 默认第一个为0
     * @param <T>         泛型，保证 head 和 data 类型的一致性
     * @throws IOException 异常
     */
    public static <T> List<T> readExcel(InputStream inputStream, Class<T> head, int sheet, Integer headRowNumber) throws IOException {
        return EasyExcel.read(inputStream, head, null)
                // 不要自动关闭，交给 Servlet 自己处理
                .autoCloseStream(false)
                .headRowNumber(headRowNumber)
                .sheet(sheet)
                .doReadSync();
    }

    /**
     * 读取Excel
     *
     * @param inputStream   文件流
     * @param head          实体类映射
     * @param sheet         sheet 默认第一个为0
     * @param headRowNumber 表格头所在行
     * @param list          返回的数据
     * @param <T>           泛型，保证 head 和 data 类型的一致性
     * @return 返回
     */
    public static <T> List<T> readExcel(InputStream inputStream, Class<T> head, int sheet, Integer headRowNumber, List<T> list) {
        ExcelReadListener<T> excelReadListener = new ExcelReadListener<T>(list);
        EasyExcel.read(inputStream, head, excelReadListener)
                // 不要自动关闭，交给 Servlet 自己处理
                .autoCloseStream(false)
                .headRowNumber(headRowNumber)
                .sheet(sheet)
                .doRead();
        return excelReadListener.getData();
    }

    /**
     * 读取Excel
     * 读取之后直接根据传入的处理类调用处理，每500条数据处理一次，不返回数据
     *
     * @param inputStream   文件流
     * @param head          实体类映射
     * @param sheet         sheet 默认第一个为0
     * @param headRowNumber 表格头所在行
     * @param consumer      处理类
     * @param <T>           泛型，保证 head 和 data 类型的一致性
     */
    public static <T> void readExcel(InputStream inputStream, Class<T> head, int sheet, Integer headRowNumber, Consumer<List<T>> consumer) {
        EasyExcel.read(inputStream, head, new PageReadListener<T>(consumer, 500))
                // 不要自动关闭，交给 Servlet 自己处理
                .autoCloseStream(false)
                .headRowNumber(headRowNumber)
                .sheet(sheet)
                .doRead();
    }

//    /**
//     * 读取 Excel(多个 sheet)
//     *
//     * @param excel    文件
//     * @param rowModel 实体类映射
//     * @return Excel 数据 list
//     */
//    public static <T> List<T> excelImport(MultipartFile excel, ExcelService excelService, Class rowModel) {
//        ExcelListener excelListener = new ExcelListener(excelService);
//        ExcelReaderBuilder readerBuilder = getReader(excel, excelListener);
//        if (readerBuilder == null) {
//            return null;
//        }
//        readerBuilder.head(rowModel).doReadAll();
//        return excelListener.getData();
//    }
}